﻿// <copyright file="MatchInfo.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>

/////////////////////////////////////////////////////////////////////////////////////////////////
// "MatchInfo".cs - A series of methods for matching a KML feed with the appropriate style file
// Project: "ICNETSOA"- "ICNETDAL"
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2010
// Author:      Don McGarry The MITRE Corporation
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)
using System;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using ICNETDAL;

namespace ICNET_KMLStyleEngine
{
  /// <summary>
  /// The possible types of style that an item can have
  /// </summary>
  public enum MatchTypeEnum
  {
    /// <summary>
    /// Unknown match
    /// </summary>
    Unknown,

    /// <summary>
    /// Styles from Cursor on Target
    /// </summary>
    CoT,

    /// <summary>
    /// Styles from Emergency Data Exchange Language  
    /// </summary>
    EDXL,

    /// <summary>
    /// The default style
    /// </summary>
    Default
  }

  /// <summary>
  /// A set of enums that determine how to match strings. 
  /// </summary>
  public enum MatchPositionEnum
  {
    /// <summary>
    /// Unknown, means that it was not possible to figure out how to match the string
    /// </summary>
    Unknown,

    /// <summary>
    /// The string starts with the value trying to be ma tched
    /// </summary>
    StartsWith,

    /// <summary>
    /// The string ends with the value trying to be matched
    /// </summary>
    EndsWith,

    /// <summary>
    /// The string exactly matches the value trying to be matched
    /// </summary>
    Exact,

    /// <summary>
    /// The string contains the pattern trying to be matched at any point inside of the string.
    /// </summary>
    Any
  }

  /// <summary>
  /// A set of enums ffor identifying where the match was found.
  /// </summary>
  public enum MatchLocationEnum
  {
    /// <summary>
    /// Unknown location
    /// </summary>
    Unknown,

    /// <summary>
    /// Indicates the match was found in the content of the entry
    /// </summary>
    Content,

    /// <summary>
    /// Indicates the match was found in the title of an 
    /// </summary>
    Title,

    /// <summary>
    /// Indicates the match was found in the title of an 
    /// </summary>
    Any
  }

  /// <summary>
  /// A series of methods for searching through entries in a KML feed and finding what style should be used for each entry.
  /// </summary>
  public class MatchInfo : IXmlSerializable
  {
    #region Public Delegates/Events

    #endregion

    #region Private Member Variables

    /// <summary>
    /// A string that is used in string matching.
    /// </summary>
    private string pattern;

    /// <summary>
    /// A location to be matched against. 
    /// </summary>
    private MatchLocationEnum? location;

    /// <summary>
    /// A position to be mtached against.
    /// </summary>
    private MatchPositionEnum? position;

   /// <summary>
   /// A type of message to be matched against.
   /// </summary>
    private MatchTypeEnum? type;

    /// <summary>
    /// The match sequence.
    /// </summary>
    private int sequence;

    #endregion

    #region Constructors

    #endregion

    #region Public Accessors

    /// <summary>
    /// Gets the MatchTypeEnum that is currently set.
    /// </summary>
    public MatchTypeEnum MatchType
    {
      get
      {
        if (this.type.HasValue)
        {
          return this.type.Value;
        }
        else
        {
          return MatchTypeEnum.Unknown;
        }
      }
    }

    /// <summary>
    /// Gets the sequence
    /// </summary>
    public int MatchSequenceNumber
    {
      get { return this.sequence; }
    }

    #endregion

    #region Public Member Functions

    /// <summary>
    /// Tries to convert a string into a MatchTypeEnum. 
    /// </summary>
    /// <param name="value">The string that should be converted.</param>
    /// <param name="retVal">A MatchPositionEnum passed in by reference, so that it can be updated with the correctly parsed enum </param>
    /// <returns>returns true if it was sucessfully converted, false if not. 
    /// retVal will be set to unknown if it cannot be sucessfully parsed</returns>
    public static bool EnumTryParse(string value, out MatchTypeEnum retVal)
    {
      try
      {
        MatchTypeEnum val = (MatchTypeEnum)Enum.Parse(typeof(MatchTypeEnum), value, true);
        retVal = val;
        return true;
      }
      catch
      {
        retVal = MatchTypeEnum.Unknown;
        return false;
      }
    }

    /// <summary>
    /// Tries to convert a string into a MatchPositionEnum. 
    /// </summary>
    /// <param name="value">The string that should be converted.</param>
    /// <param name="retVal">A MatchPositionEnum passed in by reference, so that it can be updated with the correctly parsed enum </param>
    /// <returns>returns true if it was sucessfully converted, false if not. 
    /// retVal will be set to unknown if it cannot be sucessfully parsed.</returns>
    public static bool EnumTryParse(string value, out MatchPositionEnum retVal)
    {
      try
      {
        MatchPositionEnum val = (MatchPositionEnum)Enum.Parse(typeof(MatchPositionEnum), value, true);
        retVal = val;
        return true;
      }
      catch
      {
        retVal = MatchPositionEnum.Unknown;
        return false;
      }
    }

    /// <summary>
    /// Tries to convert a string into a MatchLocationEnum. 
    /// </summary>
    /// <param name="value">The string that should be converted.</param>
    /// <param name="retVal">A MatchLocationEnum passed in by reference, so that it can be updated with the correctly parsed enum </param>
    /// <returns>Returns true if it was sucessfully converted, false if not. 
    /// retVal will be set to unknown if it cannot be sucessfully parsed</returns>
    public static bool EnumTryParse(string value, out MatchLocationEnum retVal)
    {
      try
      {
        MatchLocationEnum val = (MatchLocationEnum)Enum.Parse(typeof(MatchLocationEnum), value, true);
        retVal = val;
        return true;
      }
      catch
      {
        retVal = MatchLocationEnum.Unknown;
        return false;
      }
    }

    /// <summary>
    /// Looks at an item in the ExposureCache and attempts to determine if it is 
    /// a match with the pattern set in the global private variable "pattern" .
    /// </summary>
    /// <param name="item">an item to be compared against the predefined pattern</param>
    /// <returns>true if the match was sucessful, false if it was not. </returns>
    public bool IsAMatch(ExposureCache item)
    {
      bool isAMatch = false;

      if (this.pattern == "*.*")
      {
        return true;
      }

      switch (this.location)
      {
        case MatchLocationEnum.Unknown:
        case MatchLocationEnum.Any:
          isAMatch = this.MatchedPattern(item.Content);
          if (!isAMatch)
          {
            isAMatch = this.MatchedPattern(item.Title);
          }

          break;
        case MatchLocationEnum.Content:
          isAMatch = this.MatchedPattern(item.Content);
          break;
        case MatchLocationEnum.Title:
          isAMatch = this.MatchedPattern(item.Title);
          break;
        default:
          break;
      }

      return isAMatch;
    }

    /// <summary>
    /// Looks at an item in the ExposureCache and attempts to determine if it is 
    /// a match with the pattern set in the global private variable "pattern" .
    /// </summary>
    /// <param name="item">an item to be compared against the predefined pattern</param>
    /// <returns>true if the match was sucessful, false if it was not. </returns>
    public bool IsAMatchFromContentOrTitle(string content, string title)
    {
      bool isAMatch = false;

      if (this.pattern == "*.*")
      {
        return true;
      }

      switch (this.location)
      {
        case MatchLocationEnum.Unknown:
        case MatchLocationEnum.Any:
          isAMatch = this.MatchedPattern(content) || this.MatchedPattern(title);
          break;
        case MatchLocationEnum.Content:
          isAMatch = this.MatchedPattern(content);
          break;
        case MatchLocationEnum.Title:
          isAMatch = this.MatchedPattern(title);
          break;
        default:
          break;
      }

      return isAMatch;
    }

    /// <summary>
    /// Looks at an item in the ExposureCache and attempts to determine if it is 
    /// a match with the pattern set in the global private variable "pattern" .
    /// </summary>
    /// <param name="item">an item to be compared against the predefined pattern</param>
    /// <returns>true if the match was sucessful, false if it was not. </returns>
    public bool IsAMatchFromCotType(string cotType)
    {
      bool isAMatch = false;

      if (this.pattern == "*.*")
      {
        return true;
      }

      switch (this.location)
      {
        case MatchLocationEnum.Unknown:
        case MatchLocationEnum.Any:
          isAMatch = this.MatchedPattern(cotType);
          break;
        case MatchLocationEnum.Content:
          isAMatch = this.MatchedPattern(cotType);
          break;
        case MatchLocationEnum.Title:
          isAMatch = this.MatchedPattern(cotType);
          break;
        default:
          break;
      }

      return isAMatch;
    }

    /// <summary>
    /// Returns null.
    /// </summary>
    /// <returns>returns null.</returns>
    public XmlSchema GetSchema() 
    {
      return null; 
    }

    /// <summary>
    /// Reads the first layer of XML.
    /// </summary>
    /// <param name="reader">An XML object to be read.</param>
    public void ReadXml(XmlReader reader)
    {
      if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Match_Info")
      {
        bool isEmptyElement = reader.IsEmptyElement;

        if (!isEmptyElement)
        {
          this.sequence = 1;

          string sequence = reader["MatchSequence"];

          if (sequence != null)
          {
            int temp;
            bool canParse = Int32.TryParse(sequence, out temp);

            if (canParse)
            {
              this.sequence = temp;
            }
          }
          
          XmlReader innerReader = reader.ReadSubtree();

          this.ReadInnerXML(innerReader);

          reader.Read();
        }
      }
    }

    /// <summary>
    /// A helper method for ReadXML.
    /// </summary>
    /// <param name="reader">An XML object to be read.</param>
    public void ReadInnerXML(XmlReader reader)
    {
      bool isEmptyElement = false;

      while (reader.Read())
      {
        if (reader.MoveToContent() == XmlNodeType.Element)
        {
          isEmptyElement = reader.IsEmptyElement;

          if (!isEmptyElement)
          {
            if (reader.LocalName == "DataLocation")
            {
              bool canParse = false;
              MatchLocationEnum val;
              string elementValue = KMLStyle.GetElementStringValue(reader.ReadSubtree());

              canParse = MatchInfo.EnumTryParse(elementValue, out val);

              // bCanParse = Enum.TryParse<MatchLocationEnum>(elementValue, out val);
              if (canParse)
              {
                this.location = val;
              }
              else
              {
                // error
              }
            }
            else if (reader.LocalName == "Pattern")
            {
              this.pattern = KMLStyle.GetElementStringValue(reader.ReadSubtree());
            }
            else if (reader.LocalName == "StringPosition")
            {
              bool canParse = false;
              MatchPositionEnum val;
              string elementValue = KMLStyle.GetElementStringValue(reader.ReadSubtree());

              canParse = MatchInfo.EnumTryParse(elementValue, out val);

              // bCanParse = Enum.TryParse<MatchPositionEnum>(elementValue, out val);
              if (canParse)
              {
                this.position = val;
              }
              else
              {
                // error
              }
            }
            else if (reader.LocalName == "DataType")
            {
              bool canParse = false;
              MatchTypeEnum val;
              string elementValue = KMLStyle.GetElementStringValue(reader.ReadSubtree());

              canParse = MatchInfo.EnumTryParse(elementValue, out val);

              // bCanParse = Enum.TryParse<MatchTypeEnum>(elementValue, out val);
              if (canParse)
              {
                this.type = val;
              }
              else
              {
                // error
              }
            }
          }
        }
      }
    }

    /// <summary>
    /// Converts an XML schema into a c# object.
    /// </summary>
    /// <param name="writer">an XMLWriter that will be used to write the XML data into an object</param>
    public void WriteXml(XmlWriter writer)
    {
      writer.WriteAttributeString("MatchSequence", this.sequence.ToString());
      if (this.location != null) 
      {
        writer.WriteElementString("DataLocation", this.location.ToString()); 
      }

      if (this.pattern != null) 
      {
        writer.WriteElementString("Pattern", this.pattern.ToString()); 
      }

      if (this.position != null) 
      {
        writer.WriteElementString("StringPosition", this.position.ToString()); 
      }

      if (this.type != null) 
      {
        writer.WriteElementString("DataType", this.type.ToString()); 
      }
    }

    /// <summary>
    /// Checks to make sure that there is somtehing to be searching for.
    /// </summary>
    /// <returns>true if at least one of the potential search items has been instantiated. </returns>
    public bool HasAValue()
    {
      bool hasAValue = false;

      if (this.location != null) 
      {
        hasAValue = true; 
      }

      if (this.pattern != null) 
      {
        hasAValue = true; 
      }

      if (this.position != null) 
      {
        hasAValue = true; 
      }

      if (this.type != null) 
      {
        hasAValue = true; 
      }

      return hasAValue;
    }
    #endregion

    #region Protected Member Functions

    #endregion

    #region Private Member Functions

    /// <summary>
    /// Attempts to see if the input string contains the pattern, defined by the global variable "pattern".
    /// <remarks>The way in which the string is matched depends on the value of MatchPositionEnum.</remarks>
    /// </summary>
    /// <param name="stringToMatch">The string that you are trying to match against the pattern string.</param>
    /// <returns>true if stringToMatch was matched sucessfully. </returns>
    private bool MatchedPattern(string stringToMatch)
    {
      bool isMatchedPattern = false;

      switch (this.position)
      {
        case MatchPositionEnum.Unknown:
        case MatchPositionEnum.Any:
          isMatchedPattern = stringToMatch.Contains(this.pattern);
          break;
        case MatchPositionEnum.StartsWith:
          isMatchedPattern = stringToMatch.StartsWith(this.pattern);
          break;
        case MatchPositionEnum.EndsWith:
          isMatchedPattern = stringToMatch.EndsWith(this.pattern);
          break;
        case MatchPositionEnum.Exact:
          isMatchedPattern = stringToMatch.Equals(this.pattern);
          break;
        default:
          break;
      }

      return isMatchedPattern;
    }
    #endregion   
  }
}
